package gpstools.calc;

import gpstools.Location;
import gpstools.TrainingPoint;

import java.util.List;

import mskruch.gps.calculation.CadenceCalculator;
import mskruch.gps.calculation.Calculations;
import mskruch.gps.calculation.ElevationCalculator;
import mskruch.gps.calculation.HeartRateCalculator;
import mskruch.gps.calculation.PositionsCalculator;

/**
 * @author Marcin Skruch
 */
public class Calculator {
    private PositionsCalculator positionsCalculator;
    private HeartRateCalculator heartRateCalculator;
    private ElevationCalculator elevationCalculator;
    private CadenceCalculator cadenceCalculator;
    private PowerCalculator powerCalculator = new PowerCalculator();

    /** most recently added point */
    private TrainingPoint last = null;
    /** first point */
    private TrainingPoint first = null;
    
	private Double lastElevation;
    private double lastDistance;
    private double elevationGainDistance;
    private double elevationLossDistance;

    public Calculator() {
        positionsCalculator = Calculations.newPositionsCalculator();
        heartRateCalculator = Calculations.newHeartRateCalculator();
        elevationCalculator = Calculations.newElevationCalculator();
        cadenceCalculator = Calculations.newCadenceCalculator();
    }

    public Calculator add(TrainingPoint point) {
        positionsCalculator.add(point);
        heartRateCalculator.add(point);
        elevationCalculator.add(point);
        cadenceCalculator.add(point);
        powerCalculator.add(point);
        
        if (first == null) {
            /* first point added */
            first = point;
            lastElevation = elevationCalculator.getCurrent();
            lastDistance = positionsCalculator.getDistance();
		} else {
			/* not first point */
			if (elevationCalculator.getCurrent() != lastElevation) {
				/* elevation changed - measure the distance */
				if (elevationCalculator.getCurrent() != null
					&& lastElevation != null) {
					if (elevationCalculator.getCurrent() > lastElevation) {
						elevationGainDistance += positionsCalculator
							.getDistance() - lastDistance;
						lastDistance = positionsCalculator.getDistance();
					} else if (elevationCalculator.getCurrent() < lastElevation) {
						elevationLossDistance += positionsCalculator
							.getDistance() - lastDistance;
						lastDistance = positionsCalculator.getDistance();
					}
				}
				lastElevation = elevationCalculator.getCurrent();
			}
		}
        last = point;
        return this;
    }

    public Calculator add(TrainingPoint[] points) {
        for (TrainingPoint point : points) {
            add(point);
        }
        return this;
    }

    public double getDistance() {
        return positionsCalculator.getDistance();
    }

    public double getTime() {
        if (last == null || last == first) {
            return 0;
        }
        return ((double) last.getTime() - first.getTime()) / 1000;
    }

    public Short getAverageHeartRate() {
        return heartRateCalculator.getAverage();
    }

    public Short getMaximumHeartRate() {
        return heartRateCalculator.getMaximum();
    }

    public Short getMinimumHeartRate() {
        return heartRateCalculator.getMinimum();
    }

    public Double getCurrentElevation() {
        return elevationCalculator.getCurrent();
    }

    public double getElevationGain() {
        return elevationCalculator.getElevationGain();
    }

    public double getElevationLoss() {
        return elevationCalculator.getElevationLoss();
    }

    public double getElevationGainDistance() {
        if (last == null || last.getElevation() < lastElevation) {
            return elevationGainDistance;
        } else {
            return elevationGainDistance + (positionsCalculator.getDistance() - lastDistance);
        }
    }
    
    public double getElevationLossDistance() {
        if (last == null || last.getElevation() >= lastElevation) {
            return elevationLossDistance;
        } else {
            return elevationLossDistance + (positionsCalculator.getDistance() - lastDistance);
        }    }

    public Short getAverageCadence() {
        return cadenceCalculator.getAverage();
    }

    public Short getMaximumCadence() {
        return cadenceCalculator.getMaximum();
    }

    public Short getMinimumCadence() {
        return cadenceCalculator.getMinimum();
    }

    public Integer getAveragePower() {
        return powerCalculator.getAverage();
    }

    public Integer getMaximumPower() {
        return powerCalculator.getMaximum();
    }

    public static double distance(List<? extends Location> locations){
        PositionsCalculator pc = Calculations.newPositionsCalculator();
        for (Location location : locations) {
            pc.add(location);
        }
        return pc.getDistance();
    }
}
